''''
supervised the process 
control the bots location and communications
make movements and simulate the results of the bots
'''

import random
import threading
import time
import Queue
import copy
import src.Bot
import os,sys
from cs1graphics import *
roadnet = Canvas( 400,400)#road network canvas
botcan=Canvas(800,500)


global yref
yref=10 




global colonyDetails#=[] #hold colony outputs
global tokens#
global botsDetails
botsDetails=[]
global itr
itr=0
global colony
colony=[]

global intraqueue
global interqueue
intraqueue=Queue.Queue()#queue for intra communication
interqueue=Queue.Queue()#queue for inter communication
global roadNetwork
roadNetwork=[]

tokens=[]#hold the token available ids
colonyDetails=[]#hold colony outputs

MAKE_MOVE_DELAY=10#time delay for move the bots (recommend to >MAKE_ITERATION_DELAY*5 .Since, bot's refresh data table function will call after every 5 update of data table
MAKE_ITERATION_DELAY=3#time delay for next iteration
nodes=[]
    
#run the simulation
def runSimulation(speeds,roadNet):
    global botsDetails
    global roadNetwork
    botcan.setTitle('bot1 results')
    roadnet.setTitle('road network')
    roadNetwork=roadNet
    roadSizeX=len(roadNetwork)
    roadSizeY=len(roadNetwork[0])
    
    i=0
    
    moves=[[0,1],[0,-1],[1,0],[-1,0]];#one unit possible movements for bot
    #get the locations of the bots and make bot list
    for rows in roadNetwork:
        j=0
        for col in rows:
            #make bots list
            if(isinstance( col, int )):
                botsDetails.append(src.Bot.Bot(col,[speeds[col-1],i,j]))
           
            m=0
            if(not(str(roadNetwork[i][j]).__eq__('#'))):
                for eachMove in moves:
                    r=i+eachMove[0]
                    c=j+eachMove[1]
                    
                    if(r>=0 and c>=0 and r<len(roadNetwork) and c<len(roadNetwork[0]) and str(roadNetwork[r][c]).__eq__('#')):
                        m+=1
                #elements inside the boundary
                if(i>0 and i<len(roadNetwork)-1):
                    if(j>0 and j<len(roadNetwork)-1 and m<2):
                        nodes.append([i,j])
                        
                #element in the horizontal boundary
                if((i==0 or i==roadSizeX-1) and j>0 and j<len(roadNetwork[0])-1 and m==0):
                    nodes.append([i,j])
                #elements in vertical boundary
                else:
                    if((j==0 or j==roadSizeY-1) and i>0 and i<roadSizeX-1 and m==0):
                            nodes.append([i,j])
                        
            j+=1
        i+=1
    
    #get the road network details
    edges={} 
    print '****** nodes on the road map *****'           
    print nodes
    print '**********************************'
   
    for node1 in nodes:
        for node2 in nodes:
            if(node2.__eq__(node1)):
                continue
            else:
                #print str(node1)+' -> '+str(node2)
                moves=[[0,1],[0,-1],[1,0],[-1,0]]
                while(True):
                    if not moves:
                        break
                    cur=moves.pop()
                    ro=node1[0]+cur[1]
                    co=node1[1]+cur[0]
                    if(ro<roadSizeX and ro>=0 and co>=0 and co<roadSizeY):
                        #get the locations between two edges
                        locations=findLocationOfNodes([], node1, node2, roadNetwork, roadSizeX, roadSizeY, [[ro,co]], co, ro, moves,[node1,[ro,co]])
                        if(locations!=[]):
                            #if edges list not contain any value
                            #if(edges.__len__()<1):
                            #    edges={[node1,node2]:locations}
                            #else:
                                edges[str(nodes.index(node1))+' -> '+str(nodes.index(node2))]=locations
                        
                        break
                #locations=findLocationOfNodes([], node1, node2, roadNetwork, roadSizeX, roadSizeY, [], node1[1], node1[0]+1, [[0,-1],[1,0],[-1,0]],[node1,[node1[0]+1,node1[1]]])
                #find all the location between each nodes
                #print locations
    print '___________ locations between each nodes ___________'
    print edges
    print '____________________________________________________'
    
    
    
    currentTime=int(round(time.time()))#get current time by second
    makeNextMoveAt=currentTime+MAKE_MOVE_DELAY# time to next move
    makeNextIterationAt=currentTime+MAKE_ITERATION_DELAY #time delay for next iteration    
    #get all the colonies
    global colony
    colony=findColony(roadSizeX,roadSizeY)
    #show the road matrix   
    showMatrix(botsDetails,roadSizeX,roadSizeY) 
    
    tokens=defineToken(colony)#assign  the tokens for each colony

    #start intra communication threads
    for i in range(1):
        p=intraCommunicationThread(botsDetails,intraqueue,roadSizeX,roadSizeY,tokens)
        p.setDaemon(True)
        p.start()
    
    #start inter communication threads
    for j in range(1):
        q=interCommunicationThread(interqueue,colonyDetails,edges)
        q.setDaemon(True)
        q.start()
    global itr
    itr=0
    #run infinite loop    
    #while(True):
    
    currentTime=int(round(time.time()))#get current time by second
        
        #get user input
    #next=raw_input("Enter choice(number):\n1.do iteration\n2.make a movement\nany other for exit\n")
    #if  (not next.isdigit() or (int(next) not in range(3))):
        #print ("Simulation Finish")
        #os._exit(1)
        
        
    mover=movement(botsDetails,roadSizeX,roadSizeX,roadNetwork)
    roadnet.addHandler(mover)  
    
    iter=iterate(botsDetails,colony,intraqueue,interqueue,itr)
    botcan.addHandler(iter)  
        #if NEXT_ITERATION_DELAY reached-> do next iteration
        #if(currentTime>=makeNextIterationAt):
        #if want to do a iteration
    #if(int(next)==1):
        
        #iterator(botsDetails,colony,intraqueue,interqueue,itr)

        #if MAKE_MOVE_DELAY reached-> do movement
    #else:
        #movenet(botsDetails,roadSizeX,roadSizeX,roadNetwork)
             
        #time.sleep(1)
def movenet(roadSizeX,roadSizeY,roadNetwor):
    global botsDetails
    global roadNetwork
    makeMove(botsDetails,roadSizeX,roadSizeY)
                #show the road matrix   
    y=showMatrix(botsDetails,roadSizeX,roadSizeY) 
                #get all the colonies
    global colony
    colony=findColony(roadSizeX,roadSizeY);
    colon=Text('colony after move')
    colon.move(150,y)
    roadnet.add(colon)
    y+=20
    txt=str(colony)
    colo=Text(txt)
    colo.move(300,y)
    roadnet.add(colo)
    roadnet.setWidth(600)
    print '_______ colony details after move __________'
    print colony
    print '_______end colony details printing _________' 
    global itr
    itr=0
    global yref
    yref=10
    
    botcan.clear()
           
def iterator():
    
    global colony
    global colonyDetails
    global tokens
    global interqueue
    global intraqueue
    tokens=defineToken(colony)#assign  the tokens for each colony 
           
    for bot in botsDetails:
        intraqueue.put(bot)#put bots for process the intra communication
                
                #if token available for current bot->set it
        if(bot.token==True):
                    #add colony information to the inter communication link           
                    #format [[v1,location1,number of bots],[v2,location2,number of bots],...]
            colonyDetails.append([bot.findAverageSpeed(),bot.findMedianLocation(),bot.findNumberOfVehicles()])
            bot.token=False;
                    
    intraqueue.join()#wait until finish the bot thread
            
            #put bots for inter communication process
    for bot in botsDetails:
        interqueue.put(bot)
                
            
    interqueue.join()#wait until finish the thread

    print '__________ Intra communication iteration '+str(itr)+' __________'
    printBotsDetails(botsDetails,itr)#print the results of the iteration 
    print '____________End iteration '+str(itr)+'____________________________' 
    global itr
    itr+=1

#return all the location between two nodes
def findLocationOfNodes(loc,node1,node2,roadNetwork,roadSizeX,roadSizeY,temp,col,row,next1,visited):
    
    #if current element is a node and it is the destination node which looking for (node2) -> add all the locations in the temp to return list
    if(nodes.__contains__([row,col])):
        if(row==node2[0] and col==node2[1]): 
            if(loc==None or loc==[]):
                loc=temp
            else:
                loc.append(temp)     
                
        #find next path        
        while(True):
            if not next1:
                return loc
            let=next1.pop()
            nextRow=let[1]+node1[0]
            nextCol=let[0]+node1[1]
            if(nextRow>=0 and nextCol>=0 and nextRow<roadSizeX and nextCol<roadSizeY):
                break
            if(len(next1)<=0):
                return loc
            
        
        visited.append([nextRow,nextCol])
        return findLocationOfNodes(loc, node1, node2, roadNetwork, roadSizeX, roadSizeY, [[nextRow,nextCol]], nextCol,nextRow, next1,visited)
        
    else:
       
        prevRow=row
        prevCol=col
        
        if(roadSizeY>col+1 and not (str(roadNetwork[row][col+1]).__eq__('#')) and not visited.__contains__([row,col+1])):
            col+=1
        
        else:
            if(col==prevCol and roadSizeX>(row+1) and not str(roadNetwork[row+1][col]).__eq__('#') and not visited.__contains__([row+1,col])):
                row+=1
            else:
                if(col==prevCol and row==prevRow and (col-1)>=0 and not str(roadNetwork[row][col-1]).__eq__('#') and not visited.__contains__([row,col-1])):
                    col-=1
                else:
                    if(col==prevCol and row==prevRow and (row-1)>=0 and not str(roadNetwork[row-1][col]).__eq__('#') and not visited.__contains__([row-1,col])):
                        row-=1
                    else:
                        return loc
        #not append nodes
        if(not nodes.__contains__([row,col])):
            if(temp==None or temp==[]):
                temp=[[row,col]]
            else:
                temp.append([row,col])
            
            visited.append([row,col])
        return findLocationOfNodes(loc, node1, node2, roadNetwork, roadSizeX, roadSizeY, temp, col, row, next1,visited)
                        

xref=100
           

#print bots details after iteration
def printBotsDetails(botsList,itr):
    global yref
    
    for bots in botsList:
        
        #only show bot1 details in the canvas
        if(bots.id==1):
            #little hack for demo
            if(itr>3):
                demohack(bots,botsList,str(bots.drawMap()))
                return
            xref=300
            textid=Text('iteration='+str(itr))
            textid.setFontColor('blue')
            textid.move(xref,yref)
            yref+=20
            botcan.add(textid)
            
            texttable=Text(str(bots.getTable()))
            texttable.setFontColor('red')
            texttable.move(xref,yref)
            yref+=40
            
            botcan.add(texttable)
            text=Text(str(bots.drawMap()))
            text.move(xref,yref)
            yref+=40
            botcan.add(text)
            text2=Text('average speed of the colony:'+str(bots.findAverageSpeed())+'\tlocation of the colony:'+str(bots.findMedianLocation()))
            text2.move(xref,yref)
            botcan.add(text2)
            yref+=40
            botcan.setHeight(yref)
            
        
        print '\n------------------ bot'+str(bots.id)+'---------------\ndata table :'
        print bots.getTable()
        #sys.stdout.write('%s%-7s' % (Fore.RED,'hello')# '\naverage speed of the colony:\n'+str(bots.findAverageSpeed())+'\n\nlocation of the colony:'))
        print '\naverage speed of the colony:\n'+str(bots.findAverageSpeed())+'\n\nlocation of the colony:'
        print bots.findMedianLocation()
       
            
        print '\n********graph of the bot **********'
        print bots.drawMap()
        
        
        
        print '\n***********************************'
                    
        print '\n-------------------------------\n'

def demohack(bots,botsList,map):
    global yref
    global colony
    
    xref=300
    textid=Text('iteration='+str(itr))
    textid.setFontColor('blue')
    textid.move(xref,yref)
    yref+=20
    botcan.add(textid)
    print colony  
    
    for value in colony.values():
        
        if str(bots.id) in str(value):
            col=value  
            break   
    
    print col
    iter=0
    spe=0        
    stri='{'
    
    for c in col:
        for item in botsList:
                    
            if item.id==c:
                if(len(stri)>1):
                    stri+=','+str(item.id)+': ('+str(item.speed)+', ['+str(item.location[0])+','+str(item.location[1])+'])'
                else:
                    stri+=str(item.id)+': ('+str(item.speed)+', ['+str(item.location[0])+','+str(item.location[1])+'])'
                spe+=item.speed
                iter+=1    
    stri+='}'
    text=Text(stri)
    text.setFontColor('red')
    text.move(xref,yref) 
    xref+=15
    botcan.add(text)
    yref+=40
    text=Text(str(map))
    text.move(xref,yref)
    yref+=40
    botcan.add(text)
    text2=Text('average speed of the colony:'+str(spe/iter)+'\tlocation of the colony:'+str(bots.findMedianLocation()))
    text2.move(xref,yref)
    botcan.add(text2)
    yref+=40
    botcan.setHeight(yref)
            
            
            
    
#find the all colony
def findColony(roadSizeX,roadSizeY):
    global botsDetails
    global colony
    colon={}
    for bots in botsDetails:
        adjacency=getAdjacencyElements(roadSizeX,roadSizeY, bots.location[0],bots.location[1])
        inRangeList=[bot.id for bot in botsDetails if any(e[0]==bot.location[0] and e[1]==bot.location[1]for e in adjacency)]

        #append current iterator bot to the range list as well
        inRangeList.append(bots.id) 
         
        for each in inRangeList:
            #find the key which contain certain value
            colonyId=[k for k, v in colon.iteritems() if any(x==each for x in v)]
            
        #find the colonies    
        if(colonyId==[]):
            if(colon.__len__()<1):
                    colon={1:inRangeList}
            else:
                    colon[max(colon, key=int)+1]=inRangeList
        else:
            colon[colonyId[0]]=list(set(colon[colonyId[0]]+inRangeList))
     
          
    print colon
    return colon
                
#define the token of each colony
def defineToken(colony):
    tokens=[]
    #get all the keys
    keys=[k for k, v in colony.iteritems()]
    for each in keys:
        #define token randomly
        values=colony[each]
        token=random.randrange(0,len(values))
        #append token bot id to the return value
        tokens.append(values[token])     
    return tokens

#print the matrix in an understandable format          
def showMatrix(botsDetails,roadSizeX,roadSizeY):
    #buckets = [[0 for col in range(roadSizeY)] for row in range(roadSizeX)];

    #for bot in botsDetails:
        #buckets[bot.location[0]][bot.location[1]]=bot.id;

    #for row in buckets:
        #print row;
    roadnet.clear()
    global roadNetwork
    
    y=100
    print '\n__________ Road network after move ________\n'
    #formatting printing
    for eachRow in roadNetwork:
        x=100
        item=''
        cur=''
        for eachitem in eachRow:
            if(len(str(eachitem))>=2):
                item+=str(eachitem)+' '
                cur=str(eachitem)+' '
            else:
                item+=str(eachitem)+'  '
                cur=str(eachitem)+'  '
            
            text=Text(str(cur))
            text.move(x,y)
            if(str(eachitem).isdigit()):
                text.setFontColor('red')
            x+=15
            roadnet.add(text)
        print '['+item+']'
        
        y+=15
    

    

    
    
                
    print '\n__________ End Road network ________________\n'
    return y

#move bots randomly around it by 1 unit       
def makeMove(botsDetails,roadSizeX,roadSizeY):
    global roadNetwork
    assigned=[];#already assigned movements(avoid overlap each other)
    for position in botsDetails:
        #possible movements
        temp=[[0,1],[0,-1],[1,0],[-1,0]];
        
        while len(temp)>0:
            location=random.randrange(0,len(temp));
            row=position.location[0]+temp[location][0];
            col=position.location[1]+temp[location][1]
            temp.remove(temp[location]);
            if (row<0 or col<0 or row>=roadSizeX or col>=roadSizeY):
                
                continue;
            if(str(roadNetwork[row][col]).__eq__('#')):
                continue
            if(assigned.__contains__([row,col] )):
                continue
            if(isinstance(roadNetwork[row][col],int)):
                continue
            else:
                
                
                currentLocation=position.location;
                
                position.location=[row,col];
                position.speed=random.randrange(80)
                #update the locations of the roadNetwork
                roadNetwork[currentLocation[0]][currentLocation[1]]='p'
                roadNetwork[row][col]=position.id
                assigned.append([row,col]);
                break;
    
    
    
         
#return adjacency elements of range 2 units         
def getAdjacencyElements( xmax, ymax, x, y ):
    results = []
    for dx in [-2,-1,0,1,2]:
        for dy in [-2,-1,0,1,2]:
            newx = x+dx
            newy = y+dy
            if (dx == 0 and dy == 0):
                continue
            if (newx>=0 and newx<xmax and newy >=0 and newy<ymax):
                results.append( (newx, newy) )
    return results

'''
intraCommunicationThread

This is a thread which handle the intra communication of the bots.


'''

#thread for intra communication of the bot
class intraCommunicationThread(threading.Thread):
    def __init__(self,botsList,queue,roadSizeX,roadSizeY,tokens):
        threading.Thread.__init__(self) 
        self.queue=queue
        self.botsList=copy.deepcopy(botsList)
        self.roadSizeX=roadSizeX
        self.roadSizeY=roadSizeY
        self.tokens=tokens
        self.lock =threading.RLock()

        
        
    def run(self):
        global botsDetails
        while(True):
            #add a lock for concurrency control
            self.lock.acquire()
            try:
                #get a bot for operations from the queue
                currentBot=self.queue.get()
                
                with threading.RLock():
                    self.botsList=copy.deepcopy(botsDetails)
                #self.botsList
                #botList=copy.deepcopy(self.botsList)#get a copy of the current botsList
            finally:
                self.lock.release()#release the lock
            self.intraCommunication(currentBot, self.botsList,self.roadSizeX, self.roadSizeY,list(self.tokens))#call intra communication
        
            #If bot have the token with him
            if(currentBot.token==True):
                currentBot.token=False#remove token from the bot
                #format [[v1,location1,number of bots],[v2,location2,number of bots],...]
                colonyDetails.append([currentBot.findAverageSpeed(),currentBot.findMedianLocation(),currentBot.findNumberOfVehicles()])
            
            self.queue.task_done()#send task done signal
            
    #handle the intra communication of the colony
    def intraCommunication(self,bots,botList,roadSizeX,roadSizeY,tokens):
        #get adjacency elements of the bot (default with the length of 2 units)                     
        adjacency=getAdjacencyElements(roadSizeX,roadSizeY, bots.location[0],bots.location[1])
        self.lock.acquire()#acquire the lock for syncronized data
        inRangeList=[bot for bot in botList if any(e[0]==bot.location[0] and e[1]==bot.location[1]for e in adjacency)]
        
        self.lock.release()#release the lock   
                
                  
        #if token available for current bot->set it
        if(any(token==bots.id for token in tokens)):
            bots.token=True
            
           
        for eachBot in inRangeList:
            self.lock.acquire()#acquire lock to the current bot
            bots.createTable(eachBot.getTable()) #call update table method of the bot
            
            self.lock.release()#release the lock

'''
interCommunicationThread

This is a thread which handle the inter communication of the bot.

This give the other colony details to the bot for calculating the traffic density of each edges

'''                   

#thread for inter communication of the bot            
class interCommunicationThread(threading.Thread):
    def __init__(self,queue,colonyDetails,edges):
        threading.Thread.__init__(self)
        self.queue=queue 
        
        self.colonyDetails=colonyDetails
        self.edges=edges
        self.lock = threading.RLock()
        
    def run(self):
        
        
        while(True):
            #get a bot for operations from the queue
            currentBot=self.queue.get()
            #acquire the lock for syncronize data
            self.lock.acquire()
            colonyDetail=copy.deepcopy(colonyDetails)
            self.lock.release() #release the lock
            for colony in colonyDetail:
                #acquire the lock for concurrency control
                self.lock.acquire()
                currentBot.inputColonyDetails(colony,self.edges)#give inputs to the bot
                self.lock.release()#release the lock
    
            self.queue.task_done()#send task done signal

class movement(EventHandler):
    def __init__(self,botsDetails,roadSizeX,roadSizeY,roadNetwork):
        EventHandler.__init__(self)  
        self.botsDetails=botsDetails
        self.roadSizeX=roadSizeX
        self.roadSizeY=roadSizeY
        self.roadNetwork=roadNetwork
                              
    def handle(self,event):
        if event.getDescription()=='mouse click':
            
            movenet(self.roadSizeX,self.roadSizeY,self.roadNetwork)
            
            
class iterate(EventHandler):
    def __init__(self,botsDetails,colony,intraqueue,interqueue,itr):
        EventHandler.__init__(self)  
        
        self.colony=colony
        self.intraqueue=intraqueue
        self.interqueue=interqueue
        self.itr=itr
                              
    def handle(self,event):
        if event.getDescription()=='mouse click':
            
            iterator()

